package cc.siyecao.fastdfs.command.storage;

import cc.siyecao.fastdfs.extception.FastDfsException;
import cc.siyecao.fastdfs.model.RecvPackageInfo;
import cc.siyecao.fastdfs.model.StoreFile;
import cc.siyecao.fastdfs.protocol.ProtocolConstants;
import cc.siyecao.fastdfs.uploader.Uploader;
import cc.siyecao.fastdfs.util.BytesUtil;
import cc.siyecao.fastdfs.util.ProtocolUtil;
import cc.siyecao.fastdfs.util.StringUtils;

import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Arrays;

/**
 * 从文件上传命令
 * <p>
 * <pre>
 * 使用背景
 * 使用FastDFS存储一个图片的多个分辨率的备份时，希望只记录源图的FID，
 * 并能将其它分辨率的图片与源图关联。可以使用从文件方法
 * 名词注解:
 *   主从文件是指文件ID有关联的文件，一个主文件可以对应多个从文件
 *   主文件ID = 主文件名 + 主文件扩展名
 *   从文件ID = 主文件名 + 从文件后缀名 + 从文件扩展名
 * 以缩略图场景为例：主文件为原始图片，从文件为该图片的一张或多张缩略图
 * 流程说明：
 *  1.先上传主文件（即：原文件），得到主文件FID
 *  2.然后上传从文件（即：缩略图），指定主文件FID和从文件后缀名，上传后得到从文件FID。
 *
 * 注意:
 *   FastDFS中的主从文件只是在文件ID上有联系。FastDFS server端没有记录主从文件对应关系，
 *   因此删除主文件，FastDFS不会自动删除从文件。删除主文件后，从文件的级联删除，需要由应用端来实现。
 *
 * </pre>
 *
 * @author lyt
 */
public class UploadSlaveFileCommand extends FdfsStorageCommand<StoreFile> {

    /**
     * 文件上传命令
     *
     * @param fileSize       文件大小
     * @param masterFilename 主文件名称
     * @param prefixName     从文件前缀
     * @param fileExtName    文件扩展名
     */
    public UploadSlaveFileCommand(long fileSize, String masterFilename, String prefixName, String fileExtName, Uploader uploader) {
        this.fileSize = fileSize;
        this.fileName = masterFilename;
        this.prefixName = prefixName;
        this.fileExtName = fileExtName;
        this.uploader = uploader;
    }

    @Override
    protected void send(OutputStream out, Charset charset) throws Exception {
        byte[] header;
        byte[] extNameBs;
        byte[] sizeBytes;
        byte[] hexLenBytes;
        byte[] masterFilenameBytes;
        int offset;
        long bodyLen;
        extNameBs = new byte[ProtocolConstants.FDFS_FILE_EXT_NAME_MAX_LEN];
        Arrays.fill( extNameBs, (byte) 0 );
        if (StringUtils.isNotEmpty( fileExtName )) {
            byte[] bs = fileExtName.getBytes( charset );
            int extNameLen = bs.length;
            if (extNameLen > ProtocolConstants.FDFS_FILE_EXT_NAME_MAX_LEN) {
                extNameLen = ProtocolConstants.FDFS_FILE_EXT_NAME_MAX_LEN;
            }
            System.arraycopy( bs, 0, extNameBs, 0, extNameLen );
        }

        masterFilenameBytes = fileName.getBytes( charset );

        sizeBytes = new byte[2 * ProtocolConstants.FDFS_PROTO_PKG_LEN_SIZE];
        bodyLen = sizeBytes.length + ProtocolConstants.FDFS_FILE_PREFIX_MAX_LEN + ProtocolConstants.FDFS_FILE_EXT_NAME_MAX_LEN
                + masterFilenameBytes.length + fileSize;

        hexLenBytes = BytesUtil.long2buff( fileName.length() );
        System.arraycopy( hexLenBytes, 0, sizeBytes, 0, hexLenBytes.length );
        offset = hexLenBytes.length;
        hexLenBytes = BytesUtil.long2buff( fileSize );
        System.arraycopy( hexLenBytes, 0, sizeBytes, offset, hexLenBytes.length );

        header = ProtocolUtil.packHeader( STORAGE_PROTO_CMD_UPLOAD_SLAVE_FILE, bodyLen, (byte) 0 );
        byte[] wholePkg = new byte[(int) (header.length + bodyLen - fileSize)];
        System.arraycopy( header, 0, wholePkg, 0, header.length );
        System.arraycopy( sizeBytes, 0, wholePkg, header.length, sizeBytes.length );
        offset = header.length + sizeBytes.length;
        byte[] prefixNameBs = new byte[ProtocolConstants.FDFS_FILE_PREFIX_MAX_LEN];
        byte[] bs = prefixName.getBytes( charset );
        int prefixNameLen = bs.length;
        Arrays.fill( prefixNameBs, (byte) 0 );
        if (prefixNameLen > ProtocolConstants.FDFS_FILE_PREFIX_MAX_LEN) {
            prefixNameLen = ProtocolConstants.FDFS_FILE_PREFIX_MAX_LEN;
        }
        if (prefixNameLen > 0) {
            System.arraycopy( bs, 0, prefixNameBs, 0, prefixNameLen );
        }

        System.arraycopy( prefixNameBs, 0, wholePkg, offset, prefixNameBs.length );
        offset += prefixNameBs.length;

        System.arraycopy( extNameBs, 0, wholePkg, offset, extNameBs.length );
        offset += extNameBs.length;

        System.arraycopy( masterFilenameBytes, 0, wholePkg, offset, masterFilenameBytes.length );
        offset += masterFilenameBytes.length;

        out.write( wholePkg );
        uploader.upload( out );
    }

    @Override
    protected StoreFile receive(InputStream in, Charset charset) throws Exception {
        RecvPackageInfo pkgInfo = ProtocolUtil.recvPackage( in, STORAGE_PROTO_CMD_RESP, -1 );
        this.errno = pkgInfo.errno;
        if (pkgInfo.errno != 0) {
            return null;
        }

        if (pkgInfo.body.length <= ProtocolConstants.FDFS_GROUP_NAME_MAX_LEN) {
            throw new FastDfsException( "body length: " + pkgInfo.body.length + " <= " + ProtocolConstants.FDFS_GROUP_NAME_MAX_LEN );
        }

        String newGroupName = new String( pkgInfo.body, 0, ProtocolConstants.FDFS_GROUP_NAME_MAX_LEN ).trim();
        String remoteFileName = new String( pkgInfo.body, ProtocolConstants.FDFS_GROUP_NAME_MAX_LEN, pkgInfo.body.length - ProtocolConstants.FDFS_GROUP_NAME_MAX_LEN );
        StoreFile storeFile = new StoreFile( newGroupName, remoteFileName );
        return storeFile;
    }
}
